package com.zhuhjay.aspect;

import cn.hutool.extra.servlet.ServletUtil;
import com.zhuhjay.annotation.ApiLimiter;
import com.zhuhjay.constant.RedisConstant;
import com.zhuhjay.exception.ApiLimiterException;
import com.zhuhjay.exception.ServerErrorException;
import com.zhuhjay.utils.ServletUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @author ZhuHJay
 * @date 2023/1/29 21:07
 */
@Order(1)
@Aspect
@Component
public class ApiLimiterAspect {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Pointcut("execution(* com.zhuhjay.controller.*.*(..))")
    public void pointcut() {}

    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        ApiLimiter limiter;
        try {
            Class<?> targetClazz = joinPoint.getTarget().getClass();
            // 获取方法签名(通过此签名获取目标方法信息)
            MethodSignature ms = (MethodSignature) signature;
            // 获取目标方法上的注解指定的操作名称
            Method method = targetClazz.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
            // 获取控制器方法上的注解，判断是否存在，不存在则使用默认规则进行限流
            limiter = method.getAnnotation(ApiLimiter.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            throw new ServerErrorException();
        }
        // 获取客户端访问ip、方法名、类名
        String ip = ServletUtil.getClientIP(ServletUtils.currentRequest());
        String methodName = signature.getName();
        String className = signature.getDeclaringType().getSimpleName();
        // 拼接key
        String key = RedisConstant.LIMITER_IP_PREFIX + ip + ":" + className + "." + methodName;
        if (limiter == null) {
            defaultLimiter(key);
        } else {
            useApiLimiter(key, limiter);
        }
    }

    /** 使用 redis 进行限流 **/
    private void limiter(String key, int limit, int expire) {
        // 获取当前访问次数
        String count = stringRedisTemplate.opsForValue().get(key);
        // 如果为空，则设置初始值为1
        if (count == null) {
            stringRedisTemplate.opsForValue().set(key, "1", expire, TimeUnit.SECONDS);
        } else {
            // 如果不为空，则判断是否超过限制次数
            int currentCount = Integer.parseInt(count);
            if (currentCount >= limit) {
                throw new ApiLimiterException();
            }
            // 如果没有超过限制次数，则访问次数加1
            stringRedisTemplate.opsForValue().increment(key);
        }
    }

    /** 使用配置的 @Limiter 进行限流 **/
    private void useApiLimiter(String key, ApiLimiter limiter) {
        limiter(key, limiter.limit(), limiter.expire());
    }

    /** 默认限流规则，没有 @Limiter 注解时生效 **/
    private void defaultLimiter(String key) {
        limiter(key, 300, 60);
    }

}
